package org.dding.core;

import org.dding.core.exception.LifecycleException;
import org.dding.core.log.ILogger;
import org.dding.core.log.LoggerFactory;
import org.dding.core.util.ExceptionUtils;

/**
 * Base implementation of the {@link Lifecycle} interface that implements the
 * state transition rules for {@link Lifecycle#start()} and
 * {@link Lifecycle#stop()}
 */
public abstract class LifecycleBase implements Lifecycle {

	private static ILogger log = LoggerFactory.getLogger(LifecycleBase.class);

	/**
	 * Used to handle firing lifecycle events. TODO: Consider merging
	 * LifecycleSupport into this class.
	 */
	private LifecycleSupport lifecycle = new LifecycleSupport(this);

	/**
	 * The current state of the source component.
	 */
	private volatile LifecycleState state = LifecycleState.NEW;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addLifecycleListener(LifecycleListener listener) {
		lifecycle.addLifecycleListener(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public LifecycleListener[] findLifecycleListeners() {
		return lifecycle.findLifecycleListeners();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeLifecycleListener(LifecycleListener listener) {
		lifecycle.removeLifecycleListener(listener);
	}

	/**
	 * Allow sub classes to fire {@link Lifecycle} events.
	 * 
	 * @param type
	 *            Event type
	 * @param data
	 *            Data associated with event.
	 */
	protected void fireLifecycleEvent(String type, Object data) {
		lifecycle.fireLifecycleEvent(type, data);
	}

	@Override
	public final synchronized void init() throws LifecycleException {
		if (!state.equals(LifecycleState.NEW)) {
			invalidTransition(LifecycleState.NEW.name());
		}
		try {
			initInternal();
		} catch (Throwable t) {
			ExceptionUtils.handleThrowable(t);
			setStateInternal(LifecycleState.STOPPED, null, false);
			throw new LifecycleException("init core fail", t);
		}

		setStateInternal(LifecycleState.NEW, null, false);
	}

	protected abstract void initInternal() throws LifecycleException;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final synchronized void start() throws LifecycleException {

		if (LifecycleState.STARTED.equals(state)) {

			Exception e = new LifecycleException();
			log.debug(e, "container already started");
			return;
		}

		if (state.equals(LifecycleState.NEW)) {
			init();
		}

		try {
			startInternal();
		} catch (Throwable t) {
			ExceptionUtils.handleThrowable(t);
			setStateInternal(LifecycleState.STOPPED, null, false);
			throw new LifecycleException("init core fail", t);
		}

		setStateInternal(LifecycleState.STARTED, null, false);
	}

	/**
	 * Sub-classes must ensure that the state is changed to
	 * {@link LifecycleState#STARTING} during the execution of this method.
	 * Changing state will trigger the {@link Lifecycle#START_EVENT} event.
	 * 
	 * If a component fails to start it may either throw a
	 * {@link LifecycleException} which will cause it's parent to fail to start
	 * or it can place itself in the error state in which case {@link #stop()}
	 * will be called on the failed component but the parent component will
	 * continue to start normally.
	 * 
	 * @throws LifecycleException
	 */
	protected abstract void startInternal() throws LifecycleException;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final synchronized void stop() throws LifecycleException {

		if (LifecycleState.STOPPED.equals(state)) {

			Exception e = new LifecycleException();
			log.debug(e, "container already started");

			return;
		}

		if (state.equals(LifecycleState.NEW)) {
			state = LifecycleState.STOPPED;
			return;
		}

		if (!state.equals(LifecycleState.STARTED)) {
			invalidTransition(LifecycleState.STARTED.name());
		}

		try {
			stopInternal();
		} catch (Throwable t) {
			ExceptionUtils.handleThrowable(t);
			setStateInternal(LifecycleState.STOPPED, null, false);
			throw new LifecycleException("init core fail", t);
		}

		setStateInternal(LifecycleState.STOPPED, null, false);
	}

	/**
	 * Sub-classes must ensure that the state is changed to
	 * {@link LifecycleState#STOPPING} during the execution of this method.
	 * Changing state will trigger the {@link Lifecycle#STOP_EVENT} event.
	 * 
	 * @throws LifecycleException
	 */
	protected abstract void stopInternal() throws LifecycleException;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public LifecycleState getState() {
		return state;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getStateName() {
		return getState().toString();
	}

	/**
	 * Provides a mechanism for sub-classes to update the component state.
	 * Calling this method will automatically fire any associated
	 * {@link Lifecycle} event. It will also check that any attempted state
	 * transition is valid for a sub-class.
	 * 
	 * @param state
	 *            The new state for this component
	 */
	protected synchronized void setState(LifecycleState state)
			throws LifecycleException {
		setStateInternal(state, null, true);
	}

	/**
	 * Provides a mechanism for sub-classes to update the component state.
	 * Calling this method will automatically fire any associated
	 * {@link Lifecycle} event. It will also check that any attempted state
	 * transition is valid for a sub-class.
	 * 
	 * @param state
	 *            The new state for this component
	 * @param data
	 *            The data to pass to the associated {@link Lifecycle} event
	 */
	protected synchronized void setState(LifecycleState state, Object data)
			throws LifecycleException {
		setStateInternal(state, data, true);
	}

	private synchronized void setStateInternal(LifecycleState state,
			Object data, boolean check) throws LifecycleException {

		log.debug("the core state is " + state);

		if (check) {
			if (state == null) {
				invalidTransition("null");
				return;
			}

		}

		this.state = state;
		String lifecycleEvent = state.getLifecycleEvent();
		if (lifecycleEvent != null) {
			fireLifecycleEvent(lifecycleEvent, data);
		}
	}

	private void invalidTransition(String type) throws LifecycleException {
		String msg = "lifecyc is invalid,expect" + type + ",but now " + state;
		throw new LifecycleException(msg);
	}
}
